#include "stdafx.h"

#include "tdd.h"
#include "tddassert.h"

#include "..\SmartPtr\GlobalDefinition.h"
#include "..\SmartPtr\SmartPtr.h"
#include "..\SmartPtr\SmartPtrManager.h"
#include "..\SmartPtr\GcTool.h"
#include <algorithm>

using namespace std;

int global = 0;
bool bNeedDestroy = true;

class TestClassNoEmbed
{
public:
	int k;
	~TestClassNoEmbed(){global++; }
};

class TestClassEmbed
{
public:
	CSmartPtr<int> p;
	int k;
};

class TestB;

class TestA
{
public:
	CSmartPtr<TestB> pB;
	int k;
	TestA(int k1){k = k1;}
	~TestA(){global++;}
};

class TestB
{
public:
	CSmartPtr<TestA> pA;
	int k;
	TestB(int k1){k = k1;}
	~TestB(){global++;}
};

class TestC
{
public:
	CSmartPtr<TestC> pC;
	int k;
	TestC(int k1){k = k1;}
	~TestC(){global++;}
};

int ListContentsCount(list<LinkNode*>& listToTest)
{
	int tempCount = 0;
	for (list<LinkNode*>::iterator it = listToTest.begin();
		it != listToTest.end();
		it++)
	{
		tempCount++;
	}
	return tempCount;
};

namespace SmartPtr
{
	namespace UnitTest
	{
		TESTCLASS(CGcToolTests)
		{
			virtual void ClassInitialize()
			{
			}

			virtual void ClassCleanup()
			{
			}

			virtual void TestCleanup()
			{
				CSmartPtrManager::Destroy();
				CWrapperManager::Destroy();

				bNeedDestroy = true;

				// stop the processing
				CSmartPtrContext::SetIsCollecting(false);
			}

			TESTMETHOD(TestClassifyPtrs_normal)
			{
				CSmartPtr<int> p1 = new int;
				CSmartPtr<int> p2 = new int;
				CSmartPtr<TestClassNoEmbed> p3 = new TestClassNoEmbed;
				CSmartPtr<TestClassNoEmbed> p4 = new TestClassNoEmbed;
				CSmartPtr<TestClassEmbed> p5 = new TestClassEmbed;
				CSmartPtr<TestClassEmbed> p6 = new TestClassEmbed;

				list<LinkNode*> needGcList;
				CSmartPtrManager::GetInstance()->CollectNeedGcPtrs(0, needGcList);

				list<LinkNode*> embeddedlist;
				list<LinkNode*> usingList;
				CGcTool::ClassifySmartPtrs(needGcList, usingList, embeddedlist);

				// test count
				TDD_VERIFY(ListContentsCount(usingList) == 6);

				// test context
				TDD_VERIFY(count(usingList.begin(), usingList.end(), (LinkNode*)&p1) == 1);
				TDD_VERIFY(count(usingList.begin(), usingList.end(), (LinkNode*)&p2) == 1);
				TDD_VERIFY(count(usingList.begin(), usingList.end(), (LinkNode*)&p3) == 1);
				TDD_VERIFY(count(usingList.begin(), usingList.end(), (LinkNode*)&p4) == 1);
				TDD_VERIFY(count(usingList.begin(), usingList.end(), (LinkNode*)&p5) == 1);
				TDD_VERIFY(count(usingList.begin(), usingList.end(), (LinkNode*)&p6) == 1);

				// test count
				TDD_VERIFY(ListContentsCount(embeddedlist) == 2);

				// test context
				TDD_VERIFY(count(embeddedlist.begin(), embeddedlist.end(), (LinkNode*)&(((TestClassEmbed*)p5)->p)) == 1);
				TDD_VERIFY(count(embeddedlist.begin(), embeddedlist.end(), (LinkNode*)&(((TestClassEmbed*)p6)->p)) == 1);

				// do not need delete for the CSmartPtr is used for auto GC
			}

			TESTMETHOD(TestClassifyPtrs_empty)
			{
				list<LinkNode*> needGcList;
				CSmartPtrManager::GetInstance()->CollectNeedGcPtrs(0, needGcList);

				// test count
				TDD_VERIFY(ListContentsCount(needGcList) == 0);

				list<LinkNode*> embeddedlist;
				list<LinkNode*> usingList;
				CGcTool::ClassifySmartPtrs(needGcList, usingList, embeddedlist);

				// test count
				TDD_VERIFY(ListContentsCount(usingList) == 0);
				
				// test count
				TDD_VERIFY(ListContentsCount(embeddedlist) == 0);
			}

			TESTMETHOD(TestMark_EmptyList)
			{
				list<LinkNode*> needGcList;
				CSmartPtrManager::GetInstance()->CollectNeedGcPtrs(0, needGcList);

				list<LinkNode*> embeddedlist;
				list<LinkNode*> userReachableList;
				CGcTool::ClassifySmartPtrs(needGcList, userReachableList, embeddedlist);

				// mark ptrs
				list<LinkNode*> usingPtrsList;
				CGcTool::Mark(userReachableList, embeddedlist, usingPtrsList);

				// test count
				TDD_VERIFY(ListContentsCount(usingPtrsList) == 0);
			}

			TESTMETHOD(TestMark_AllMark)
			{
				CSmartPtr<int> p1 = new int;
				CSmartPtr<int> p2 = new int;
				CSmartPtr<TestClassNoEmbed> p3 = new TestClassNoEmbed;
				CSmartPtr<TestClassNoEmbed> p4 = new TestClassNoEmbed;
				CSmartPtr<TestClassEmbed> p5 = new TestClassEmbed;
				CSmartPtr<TestClassEmbed> p6 = new TestClassEmbed;

				p5->p = new int;
				p6->p = new int;

				list<LinkNode*> needGcList;
				CSmartPtrManager::GetInstance()->CollectNeedGcPtrs(0, needGcList);

				list<LinkNode*> embeddedlist;
				list<LinkNode*> userReachableList;
				CGcTool::ClassifySmartPtrs(needGcList, userReachableList, embeddedlist);

				// mark ptrs
				list<LinkNode*> usingPtrsList;
				CGcTool::Mark(userReachableList, embeddedlist, usingPtrsList);

				// exclude NULL
				TDD_VERIFY(ListContentsCount(usingPtrsList) == 6 + 2/*p5->p=new int*/);

				// test context
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p1) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p2) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p3) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p4) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p5) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p6) == 1);

				// test context
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&(((TestClassEmbed*)p5)->p)) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&(((TestClassEmbed*)p6)->p)) == 1);
			}

			TESTMETHOD(TestMark_SomeUnMark)
			{
				CSmartPtr<int> p1 = new int;
				CSmartPtr<int> p2 = new int;
				p1 = p2; // the original p1 lost
				CSmartPtr<TestClassNoEmbed> p3 = new TestClassNoEmbed;
				CSmartPtr<TestClassNoEmbed> p4 = new TestClassNoEmbed;
				p3 = p4; // the original p3 lost
				CSmartPtr<TestClassEmbed> p5 = new TestClassEmbed;
				CSmartPtr<TestClassEmbed> p6 = new TestClassEmbed;
				p5 = p6; // the original p5 lost
				
				p5->p = new int; // just use the original p6's inner CSmartPtr

				list<LinkNode*> needGcList;
				CSmartPtrManager::GetInstance()->CollectNeedGcPtrs(0, needGcList);
				// when doing "p5 = p6", the CSmartPtr inside p5 has been deleted,
				// so there are totally 8 - 1 CSmartPtr survive now
				TDD_VERIFY(ListContentsCount(needGcList) == 7);

				list<LinkNode*> embeddedlist;
				list<LinkNode*> userReachableList;
				CGcTool::ClassifySmartPtrs(needGcList, userReachableList, embeddedlist);

				// mark ptrs
				list<LinkNode*> usingPtrsList;
				CGcTool::Mark(userReachableList, embeddedlist, usingPtrsList);

				// usingPtrsList only contain those currently in use ptrs, exclude Null
				TDD_VERIFY(ListContentsCount(usingPtrsList) == 6 + 1);

				// test context
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p1) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p2) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p3) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p4) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p5) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p6) == 1);

				// null not included in the usingPtrsList
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&(((TestClassEmbed*)p5)->p)) == 1);
				// the following will be true, just because p6 and p5 points to the same object
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&(((TestClassEmbed*)p6)->p)) == 1);
			}

			TESTMETHOD(TestMark_InnerPointersToSame)
			{
				CSmartPtr<TestClassEmbed> p1 = new TestClassEmbed;
				CSmartPtr<TestClassEmbed> p2 = new TestClassEmbed;

				int *pInt = new int(2);

				p1->p = pInt;
				p2->p = pInt;

				list<LinkNode*> needGcList;
				CSmartPtrManager::GetInstance()->CollectNeedGcPtrs(0, needGcList);
				TDD_VERIFY(ListContentsCount(needGcList) == 4);

				list<LinkNode*> embeddedlist;
				list<LinkNode*> userReachableList;
				CGcTool::ClassifySmartPtrs(needGcList, userReachableList, embeddedlist);

				// mark ptrs
				list<LinkNode*> usingPtrsList;
				CGcTool::Mark(userReachableList, embeddedlist, usingPtrsList);

				// usingPtrsList only contain those currently in use ptrs, exclude Null
				// that is p1, p2 and pInt
				TDD_VERIFY(ListContentsCount(usingPtrsList) == 3);

				// test context
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p1) == 1);
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&p2) == 1);

				// null not included in the usingPtrsList
				TDD_VERIFY(count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&(((TestClassEmbed*)p1)->p)) == 1 
					|| count(usingPtrsList.begin(), usingPtrsList.end(), (LinkNode*)&(((TestClassEmbed*)p2)->p)) == 1);

				TDD_VERIFY(*(p1->p) == *(p2->p) && *(p1->p) == 2);
				TDD_VERIFY((int*)(p1->p) == (int*)(p2->p));
			}

			TESTMETHOD(TestGc_NoCycle)
			{
				// the sign to indicate the de-constructor has been done
				global = 0;

				CSmartPtr<TestClassNoEmbed> p1 = new TestClassNoEmbed();
				CSmartPtr<TestClassNoEmbed> p2 = new TestClassNoEmbed();

				// let the original p1 and p2 pointer object to be hanging
				// the p1 and p2 will be deleted before assign to NULL
				p1 = NULL;
				p2 = NULL;

				TDD_VERIFY( 2 == global );

				list<LinkNode*> needGcList;
				CSmartPtrManager::GetInstance()->CollectNeedGcPtrs(0, needGcList);
				TDD_VERIFY(ListContentsCount(needGcList) == 2/*user reach*/);

				list<LinkNode*> embeddedlist;
				list<LinkNode*> userReachableList;
				CGcTool::ClassifySmartPtrs(needGcList, userReachableList, embeddedlist);

				// mark ptrs
				list<LinkNode*> usingPtrsList;
				CGcTool::Mark(userReachableList, embeddedlist, usingPtrsList);

				// GC
				TDD_VERIFY( 0 == CGcTool::DoGcWork(embeddedlist) );
			}

			//// Attention that the GC thread is specially to deal with cursive ref
			//TESTMETHOD(TestGc_Cycle)
			//{
			//	global = 0;

			//	CSmartPtr<TestA> p1 = new TestA(1);
			//	CSmartPtr<TestB> p2 = new TestB(2);

			//	p1->pB = p2;
			//	p2->pA = p1;

			//	void* addP1 = &p1;
			//	void* addP2 = &p2;
			//	void* addP1pB = &(p1->pB);
			//	void* addP2pA = &(p2->pA);

			//	// let the original p1 and p2 pointer object to be hanging
			//	p1 = NULL;
			//	p2 = NULL;

			//	// set the signal that the GC is processing 
			//	CSmartPtrContext::SetIsCollecting(true);

			//	list<LinkNode*> needGcList;
			//	CSmartPtrManager::GetInstance()->CollectNeedGcPtrs(0, needGcList);

			//	list<LinkNode*> embeddedlist;
			//	list<LinkNode*> userReachableList;
			//	CGcTool::ClassifySmartPtrs(needGcList, userReachableList, embeddedlist);

			//	// mark ptrs
			//	list<LinkNode*> usingPtrsList;
			//	CGcTool::Mark(userReachableList, embeddedlist, usingPtrsList);

			//	// GC
			//	TDD_VERIFY( 2 == CGcTool::DoGcWork(embeddedlist) );
			//	// TDD_VERIFY( 2 == global);

			//	bNeedDestroy = false;
			//}
		};
	}
}